ArrayList<HashMap<String, String>> 儲存使用者資料getData, setData)LoginData 用雙重鎖定(Double-Checked Locking)實作 thread-safe 的單例以下是完整程式碼及註解,另外,在這個範例中,我們「假裝」使用了資料庫,其實是用一個ArrayList來儲存假資料,也就是說資料並沒有真的存入硬碟或資料庫系統,而只是在記憶體中的一個資料結構裡暫存,但當 App 關閉或程式重啟時,記憶體中的資料會消失,不會保存到磁碟上,因此不算是真正意義上的資料庫
public class MainActivity extends AppCompatActivity {
private Button saveButton, loginButton;
private EditText emailEditText, usernameEditText, passwordEditText;
private LoginData loginData = LoginData.getInstance(); // 連接 LoginData
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
bindUI();
}
protected void bindUI() {
saveButton = findViewById(R.id.main_save_btn);
loginButton = findViewById(R.id.main_login_btn);
emailEditText = findViewById(R.id.main_email_et);
usernameEditText = findViewById(R.id.main_username_et);
passwordEditText = findViewById(R.id.main_password_et);
// TextWatcher: 監聽輸入框,輸入框內容一改就自動檢查
TextWatcher watcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
check();
}
@Override
public void afterTextChanged(Editable s) { }
};
// 讓 email、username、password 這三個 EditText 的內容一有變動,都會自動呼叫 check()
emailEditText.addTextChangedListener(watcher);
usernameEditText.addTextChangedListener(watcher);
passwordEditText.addTextChangedListener(watcher);
// 一開始就檢查一次
check();
// 將點擊事件獨立出去
saveButton.setOnClickListener(this::onSaveButtonClick);
loginButton.setOnClickListener(this::onLoginButtonClick);
// 預先建立管理員帳號
if (loginData.getData().isEmpty()) {
HashMap<String, String> admin = new HashMap<>();
admin.put("username", "admin");
admin.put("email", "admin@gmail.com");
admin.put("password", "123456");
loginData.getData().add(admin);
}
}
// 清空輸入欄位
private void clear() {
emailEditText.setText("");
usernameEditText.setText("");
passwordEditText.setText("");
emailEditText.setError(null);
passwordEditText.setError(null);
}
private void check() {
String username = usernameEditText.getText().toString();
String email = emailEditText.getText().toString();
String password = passwordEditText.getText().toString();
boolean isAllFilled = !email.isEmpty() && !password.isEmpty() && !username.isEmpty();
boolean isEmail = Patterns.EMAIL_ADDRESS.matcher(email).matches();
boolean isPassword = Pattern.matches("\\d{6,}", password); // \\d:必須是數字 {6,}:至少連續6個字
emailEditText.setOnFocusChangeListener((v, hasFocus) -> {
if (!hasFocus) {
if (!isEmail && !email.isEmpty()) {
emailEditText.setError("電子郵件格式錯誤");
} else {
emailEditText.setError(null);
}
}
});
passwordEditText.setOnFocusChangeListener((v, hasFocus) -> {
if (!hasFocus) {
if (!isPassword && !password.isEmpty()) {
passwordEditText.setError("密碼需至少6碼數字");
} else {
passwordEditText.setError(null);
}
}
});
saveButton.setEnabled(isAllFilled && isEmail && isPassword);
loginButton.setEnabled(isAllFilled && isEmail && isPassword);
}
private void onSaveButtonClick(View view) {
String username = usernameEditText.getText().toString();
String email = emailEditText.getText().toString();
String password = passwordEditText.getText().toString();
check();
// 用 for 迴圈逐一檢查目前所有已經註冊的用戶資料(每個 user 是一個 HashMap)
for (HashMap<String, String> user : loginData.getData()) {
// 如果 username 重複
if (username.equals(user.get("username"))) {
Toast.makeText(this, "該用戶名稱已被占用", Toast.LENGTH_SHORT).show();
return; // 直接結束,不新增
}
// 如果 email 重複
if (email.equals(user.get("email"))) {
Toast.makeText(this, "該 email 已被占用", Toast.LENGTH_SHORT).show();
return; // 直接結束,不新增
}
}
if (!username.isEmpty() && !email.isEmpty() && !password.isEmpty()) {
// 沒有重複且欄位都有填寫才新增
HashMap<String, String> newUser = new HashMap<>();
newUser.put("username", username);
newUser.put("email", email);
newUser.put("password", password);
loginData.getData().add(newUser);
clear();
Toast.makeText(this, "已儲存資料", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "請填寫完整", Toast.LENGTH_SHORT).show();
}
}
private void onLoginButtonClick(View view) {
String username = usernameEditText.getText().toString();
String email = emailEditText.getText().toString();
String password = passwordEditText.getText().toString();
check();
if (!username.isEmpty() && !email.isEmpty() && !password.isEmpty()) {
// 比較 LoginData 物件中的資料,和畫面上用戶在欄位中輸入的文字是否一樣(儲存多筆資料)
boolean found = false;
for (HashMap<String, String> user : loginData.getData()) {
if (user.get("email").equals(email) &&
user.get("password").equals(password) &&
user.get("username").equals(username)) {
found = true;
break;
}
}
// 如果找到對應資料
if (found) {
// 登入
Intent intent = new Intent(this, HomeActivity.class);
// 傳輸資料
intent.putExtra("username", username);
startActivity(intent);
Toast.makeText(this, "Hello,"+username, Toast.LENGTH_SHORT).show();
clear();
} else {
// 登入失敗
clear();
Toast.makeText(this, "登入失敗", Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(this, "請填寫完整", Toast.LENGTH_SHORT).show();
}
}
}
public class HomeActivity extends AppCompatActivity {
private Button getdataButton, backButton;
private TextView showdataTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_home);
bindUI();
}
protected void bindUI() {
getdataButton = findViewById(R.id.home_getdata_btn);
backButton = findViewById(R.id.home_back_btn);
showdataTextView = findViewById(R.id.home_showdata_tv);
// 取得 MainActivity 傳來的 Intent
Intent intent = getIntent();
String username = intent.getStringExtra("username");
getdataButton.setOnClickListener(view -> {
LoginData loginData = LoginData.getInstance();
StringBuilder sb = new StringBuilder();
if ("admin".equals(username)) {
int index = 1;
for (HashMap<String, String> user : loginData.getData()) {
sb.append(String.format("帳號%d:\n用戶名:%s\n電子郵件:%s\n密碼:%s\n\n",
index++,
user.get("username"),
user.get("email"),
user.get("password")));
}
} else {
// 只顯示自己
for (HashMap<String, String> user : loginData.getData()) {
if (username.equals(user.get("username"))) {
sb.append(String.format("用戶名:%s\n電子郵件:%s\n密碼:%s\n",
user.get("username"),
user.get("email"),
user.get("password")));
break;
}
}
}
showdataTextView.setText(sb.toString());
});
backButton.setOnClickListener(v -> finish());
}
}
public class LoginData {
private static LoginData instance; // 單例實例變數,用來保存唯一的 LoginData 物件
private String username; // 用戶名
private String email; // 電子郵件
private String password; // 密碼
private String phone; // 電話
private ArrayList<HashMap<String, String>> data = new ArrayList<HashMap<String, String>>();
private LoginData() {} // 私有建構子,外部不能用 new LoginData() 新建物件,只能透過 getInstance() 取得唯一實例
public static LoginData getInstance() {
if (instance == null) {
synchronized (LoginData.class) {
if (instance == null) {
instance = new LoginData();
}
}
}
return instance;
}
// getter 和 setter 方法
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
public ArrayList<HashMap<String, String>> getData() {
return data;
}
public void setData(ArrayList<HashMap<String, String>> data) {
this.data = data;
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="40dp"
android:orientation="vertical"
android:padding="16dp">
<EditText
android:id="@+id/main_email_et"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Email"
android:inputType="textEmailAddress" />
<EditText
android:id="@+id/main_username_et"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Username"
android:inputType="text" />
<EditText
android:id="@+id/main_password_et"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Password"
android:inputType="textPassword" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/main_save_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="儲存" />
<Button
android:id="@+id/main_login_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="登入" />
</LinearLayout>
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
android:gravity="center_horizontal">
<TextView
android:id="@+id/home_showdata_tv"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center_horizontal|top"
android:paddingTop="40dp"
android:text="資料顯示處"
android:textSize="20sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingTop="24dp"
android:paddingStart="40dp"
android:paddingEnd="40dp"
android:gravity="center_horizontal">
<Button
android:id="@+id/home_getdata_btn"
android:layout_width="wrap_content"
android:layout_height="70dp"
android:text="獲取資料"
android:textSize="20sp"
android:layout_marginEnd="24dp"/>
<Button
android:id="@+id/home_back_btn"
android:layout_width="wrap_content"
android:layout_height="70dp"
android:text="返回上頁"
android:textSize="20sp"/>
</LinearLayout>
</LinearLayout>
檔案結構圖:
